/*
 * Copyright 2012 Gregor Schauer
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package at.schauer.gregor.vienna.convert.vienna.lang;

import at.schauer.gregor.vienna.convert.vienna.base.AbstractContainerConverter;
import at.schauer.gregor.vienna.exception.ViennaException;
import at.schauer.gregor.vienna.serializer.Serializer;
import at.schauer.gregor.vienna.util.ViennaUtils;
import com.google.common.collect.Iterators;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.reflect.FieldUtils;

import java.util.Iterator;

/**
 * @author Gregor Schauer
 */
public class ThrowableConverter<T extends Throwable> extends AbstractContainerConverter<T, StackTraceElement> {
	public static final String DIVIDER = Character.valueOf((char) 0).toString();

	public ThrowableConverter() {
		super("„", "“");
	}

	@Override
	@SuppressWarnings("unchecked")
	public T sourceToTarget(String source, Class<? extends T> targetClass) {
		String[] parts = getParts(source);
		parts[2] = stripContainerSequences(parts[2]);
		String[] split = split(parts[2], getSeparator());

		T instance = createInstance(targetClass, split.length);
		StackTraceElement[] stackTrace = new StackTraceElement[split.length];

		try {
			FieldUtils.writeField(instance, "stackTrace", stackTrace, true);
			FieldUtils.writeField(instance, "detailMessage", parts[0], true);

			if (!parts[1].isEmpty()) {
				String value = ViennaUtils.ConverterUtils.valueFromString(parts[1]);
				Class<? extends T> type = (Class<? extends T>) ClassUtils.getClass(ViennaUtils.ConverterUtils.typeFromString(parts[1]));
				FieldUtils.writeField(instance, "cause", sourceToTarget(value, type), true);
			}
		} catch (Exception e) {
			throw new ViennaException(e);
		}

		for (int i = 0; i < split.length; i++) {
			stackTrace[i] = deserialize(split[i]);
		}

		return instance;
	}

	protected String[] getParts(String str) {
		String[] parts = new String[3];
		int pos = str.indexOf(DIVIDER);
		parts[0] = str.substring(0, pos);
		parts[1] = str.substring(pos + DIVIDER.length(), str.lastIndexOf(DIVIDER));
		parts[2] = str.substring(str.lastIndexOf(DIVIDER) + DIVIDER.length());
		return parts;
	}

	@Override
	public String targetToSource(T target) {
		StringBuilder builder = new StringBuilder();
		builder.append(target.getMessage()).append(DIVIDER);

		if (target.getCause() != null) {
			builder.append(ViennaUtils.ConverterUtils.type(target.getCause().getClass()));
			builder.append(Serializer.CLASS_OBJECT_SEPARATOR);
			builder.append(ViennaUtils.SerializationUtils.save(ctx, target.getCause()));
		}
		builder.append(DIVIDER);
		builder.append(super.targetToSource(target).replace(StackTraceElement.class.getName() + Serializer.CLASS_OBJECT_SEPARATOR, ""));
		return builder.toString();
	}

	@Override
	protected void add(T instance, int i, StackTraceElement elem) {
		// This method is not called by this implementation.
		try {
			((StackTraceElement[]) FieldUtils.readField(instance, "stackTrace", true))[i] = elem;
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	@SuppressWarnings("unchecked")
	protected <E> E deserialize(String str) {
		return (E) ViennaUtils.DeserializationUtils.load(ctx, StackTraceElement.class, str);
	}

	@Override
	protected Iterator<StackTraceElement> getElements(T target) {
		return Iterators.forArray(target.getStackTrace());
	}
}
